#include <os/fs.h>
#include <os/mm.h>
#include <os/string.h>
#include <sbi.h>
#include <pgtable.h>
#include <stdio.h>

fd_t fds[32];

void make_index(uint32_t block_size, inode_t* inode)
{
    uint32_t block_id;
    uint32_t i = 0, j = 0;
    uint64_t buffer_kva1;
    uint64_t buffer_kva2;

    if(block_size <= DIRECT_INDEX_MAX)
    {
        /* use direct index */
        while(i < block_size)
        {
            block_id = allocBlock();
            inode->direct[i] = block_id;
            i++;
        }
    }
    else if(block_size <= FIRST_INDEX_MAX)
    {
        /* use first index */
        inode->first = allocBlock();
        buffer_kva1 = allocPage();
        while(i < block_size)
        {
            block_id = allocBlock();
            *((uint32_t*)buffer_kva1 + i) = block_id;
            i++;
        }

        /* write back first index blcok to sd card */
        sbi_sd_write((uint32_t)kva2pa(buffer_kva1), SECTORS_PER_BLOCK, inode->first * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);
        freePage(buffer_kva1);
    }
    else if(block_size <= SECOND_INDEX_MAX)
    {
        uint32_t first_block_id;
        inode->second = allocBlock();
        buffer_kva2 = allocPage();

        while(i < block_size)
        {
            /* make first index */
            first_block_id = allocBlock();
            buffer_kva1 = allocPage();
            while(i < block_size && i < FIRST_INDEX_MAX)
            {
                block_id = allocBlock();
                *((uint32_t*)buffer_kva1 + i) = block_id;
                i++;
            }

            /* write back first index blcok to sd card */
            sbi_sd_write((uint32_t)kva2pa(buffer_kva1), SECTORS_PER_BLOCK, first_block_id * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);
            freePage(buffer_kva1);

            /* renew second index */
            *((uint32_t*)buffer_kva2 + j) = first_block_id;
            j++;
            block_size -= i;
            i = 0;
        }

        /* write back second index blcok to sd card */
        sbi_sd_write((uint32_t)kva2pa(buffer_kva2), SECTORS_PER_BLOCK, inode->second * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);
        freePage(buffer_kva2);
    }
}

/* get block id of given pos according to index map */
int search_index(uint32_t pos, inode_t *inode)
{
    uint32_t block_cnt;
    uint32_t block_id;
    block_cnt = pos / 0x1000;

    /* according to index ways */
    if(inode->used_blocks <= DIRECT_INDEX_MAX)
        block_id =  inode->direct[block_cnt];
    else if(inode->used_blocks <= FIRST_INDEX_MAX)
    {
        uint64_t first_kva;

        /* load first index map */
        first_kva = allocPage();
        sbi_sd_read((uint32_t)kva2pa(first_kva), SECTORS_PER_BLOCK, inode->first * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        /* read data */
        block_id = *((uint32_t*)first_kva + block_cnt);

        freePage(first_kva);
    }
    else if(inode->used_blocks <= SECOND_INDEX_MAX)
    {
        uint64_t first_kva;
        uint64_t second_kva;
        uint32_t first_cnt;
        uint32_t first_blockid;

        /* load second index map */
        second_kva = allocPage();
        sbi_sd_read((uint32_t)kva2pa(second_kva), SECTORS_PER_BLOCK, inode->second * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        /* load first index map */
        first_kva = allocPage();
        first_cnt = block_cnt / 0x400;
        first_blockid = *((uint32_t*)second_kva + first_cnt);
        sbi_sd_read((uint32_t)kva2pa(first_kva), SECTORS_PER_BLOCK, first_blockid * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        /* read data */
        block_id = *((uint32_t*)first_kva + block_cnt % 0x400);

        freePage(first_kva);
        freePage(second_kva);
    }
    
    return block_id;
}

int allocFd()
{
    uint32_t i = 0;
    while(fds[i].used)
        i++;
    fds[i].used = 1;
    return i;
}

int do_mkfile(char *name, uint32_t block_size)
{
    uint32_t i = 0;
    
    /* find if name is used */
    while(i < 127)
    {
        if(!kstrcmp(cur_dentry->content[i].name, name))
            return 0;
        i++;
    }

    /* find a unused pos */
    i = 0;
    while(cur_dentry->content[i].type != 0)
        i++;

    /* create inode */
    uint32_t inode_num;
    inode_t *inode_ptr;
    inode_num = allocInode();
    inode_ptr = allocPage();
    inode_ptr->inode_num = inode_num;
    inode_ptr->type = TYPE_FILE;
    inode_ptr->link = 0;
    inode_ptr->used_size = 0;
    inode_ptr->used_blocks = block_size;
    inode_ptr->ref_num = 1;
    make_index(block_size, inode_ptr);

    /* write inode to sd card */
    write_inode(inode_num, inode_ptr);
    freePage(inode_ptr);

    /* renew father dentry */
    cur_dentry->content[i].type = TYPE_FILE;
    kstrcpy(cur_dentry->content[i].name, name);
    cur_dentry->content[i].inode_idx = inode_num;
    sbi_sd_write((uint32_t)kva2pa(cur_dentry), SECTORS_PER_BLOCK, cur_dentry_inode->direct[0] * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

}

int do_openfile(char *name, uint32_t access)
{
    uint32_t i = 0;
    uint32_t fd_num;
    
    /* find the file */
    while(i < 127)
    {
        if(!kstrcmp(cur_dentry->content[i].name, name))
            break;
        i++;
    }

    /* create a fd */
    fd_num = allocFd();
    fds[fd_num].inode_num = cur_dentry->content[i].inode_idx;
    fds[fd_num].availability = (uint16_t)access;
    fds[fd_num].read_pos = 0;
    fds[fd_num].write_pos = 0;

    return fd_num;
}

int do_readfile(uint32_t fd, char *buff, uint32_t size)
{
    /* judge if have the access */
    if(fds[fd].availability == RDONLY || fds[fd].availability == RDWR)
    {
        /* read inode of file */
        inode_t *inode;
        inode = allocPage();
        read_inode(fds[fd].inode_num, inode);

        /* read data of file */
        uint64_t buffer_kva;
        uint32_t block_id;
        uint32_t offset;
        buffer_kva = allocPage();
        block_id = search_index(fds[fd].read_pos, inode);
        offset = fds[fd].read_pos % 0x1000;
        sbi_sd_read((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, block_id * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        /* copy data */
        kmemcpy(buff, buffer_kva + offset, size);
        fds[fd].read_pos += size;

        /* reclaim space */
        freePage(buffer_kva);
        freePage(inode);

        return size;
    }

    return 0;
}

int do_writefile(uint32_t fd, char *buff, uint32_t size)
{
    /* judge if have the access */
    if(fds[fd].availability == WRONLY || fds[fd].availability == RDWR)
    {
        /* read inode of file */
        inode_t *inode;
        inode = allocPage();
        read_inode(fds[fd].inode_num, inode);

        /* read data of file */
        uint64_t buffer_kva;
        uint32_t block_id;
        uint32_t offset;
        buffer_kva = allocPage();
        block_id = search_index(fds[fd].write_pos, inode);
        offset = fds[fd].write_pos % 0x1000;
        sbi_sd_read((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, block_id * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        /* write data and save in sd card */
        kmemcpy(buffer_kva + offset, buff, size);
        fds[fd].write_pos += size;
        sbi_sd_write((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, block_id * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        /* renew inode */
        if(inode->link)
        {
            /* deal with if the inode linked to another */
            inode_t *linkinode;
            linkinode = allocPage();
            read_inode(inode->link, linkinode);
            inode->used_size = linkinode->used_size;
            linkinode->used_size += size;
            write_inode(inode->link, linkinode);
            freePage(linkinode);
        }
        inode->used_size += size;
        write_inode(inode->inode_num, inode);

        /* reclaim space */
        freePage(buffer_kva);
        freePage(inode);

        return size;
    }

    return 0;
}

void do_closefile(uint32_t fd)
{
    fds[fd].used = 0;
}

/* print the first block data; only used for test */
void do_cat(char *name)
{
    uint32_t i = 0;
    
    /* find the file */
    uint32_t inode_num;
    inode_num = find_inode(name);

    /* check if file */
    inode_t *inode_ptr;
    inode_ptr = allocPage();
    read_inode(inode_num, inode_ptr);
    if(inode_ptr->type == TYPE_FILE)
    {
        uint64_t buffer_kva;
        uint32_t block_id;
        uint32_t j;

        if(inode_ptr->link)
        {
            /* deal with if the inode linked to another */
            inode_t *linkinode;
            linkinode = allocPage();
            read_inode(inode_ptr->link, linkinode);
            inode_ptr->used_size = linkinode->used_size;
            write_inode(inode_num, inode_ptr);
            freePage(linkinode);
        }

        /* load data block */
        buffer_kva = allocPage();
        block_id = search_index(0, inode_ptr);
        sbi_sd_read((uint32_t)kva2pa(buffer_kva), SECTORS_PER_BLOCK, block_id * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        /* print the file */
        for(j = 0; j < inode_ptr->used_size; j++)
            prints("%c", ((char*)buffer_kva)[j]);
        
        /* reclaim space */
        freePage(buffer_kva);
        freePage(inode_ptr);
    }
}

void do_ln(char *src, char *dest)
{
    /* find src file's inode */
    uint32_t src_inode_num;
    src_inode_num = find_inode(src);

    /* renew source inode */
    inode_t *inode_ptr;
    inode_ptr = allocPage();
    read_inode(src_inode_num, inode_ptr);

    /* only file type can build a hard link */
    if(inode_ptr->type == TYPE_FILE)
    {
        /* build link in current dentry */
        /* find if name is used */
        uint32_t i;
        while(i < 127)
        {
            if(!kstrcmp(cur_dentry->content[i].name, dest))
                return 0;
            i++;
        }

        /* find a unused pos */
        i = 0;
        while(cur_dentry->content[i].type != 0)
            i++;

        /* add inode */
        cur_dentry->content[i].type = TYPE_FILE;
        cur_dentry->content[i].inode_idx = src_inode_num;
        kstrcpy(cur_dentry->content[i].name, dest);
        sbi_sd_write((uint32_t)kva2pa(cur_dentry), SECTORS_PER_BLOCK, cur_dentry_inode->direct[0] * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

        /* renew source inode */
        inode_ptr->ref_num += 1;
        write_inode(src_inode_num, inode_ptr);
    }

    freePage(inode_ptr);
}

void do_ln_s(char *src, char *dest)
{
    /* find src file's inode */
    uint32_t src_inode_num;
    src_inode_num = find_inode(src);

    /* build a new inode */
    inode_t *src_inode;
    inode_t *dest_inode;
    src_inode = allocPage();
    dest_inode = allocPage();

    /* copy inode */
    read_inode(src_inode_num, src_inode);
    kmemcpy(dest_inode, src_inode, 64);
    dest_inode->inode_num = allocInode();
    dest_inode->link = src_inode_num;
    if(src_inode->type == TYPE_FILE)
        dest_inode->ref_num = 1;
    else
        dest_inode->ref_num = 0;
    write_inode(dest_inode->inode_num, dest_inode);

    /* build link in current dentry */
    /* find if name is used */
    uint32_t i;
    while(i < 127)
    {
        if(!kstrcmp(cur_dentry->content[i].name, dest))
            return 0;
        i++;
    }

    /* find a unused pos */
    i = 0;
    while(cur_dentry->content[i].type != 0)
        i++;

    /* add inode */
    cur_dentry->content[i].type = dest_inode->type;
    cur_dentry->content[i].inode_idx = dest_inode->inode_num;
    kstrcpy(cur_dentry->content[i].name, dest);
    sbi_sd_write((uint32_t)kva2pa(cur_dentry), SECTORS_PER_BLOCK, cur_dentry_inode->direct[0] * SECTORS_PER_BLOCK + SUPERBLOCK_START_SECTOR);

    freePage(src_inode);
    freePage(dest_inode);
}

void do_movepos(uint32_t fd, uint32_t type, uint32_t pos) // type 0 for read, type 1 for write
{
    if(type == 0)
        fds[fd].read_pos = pos;
    else
        fds[fd].write_pos = pos;
}